﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Sparklings.LevelElements;
using Sparklings.RayTracing;
using Sparklings.Rendering;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sparklings
{

    internal class SparklingMoveNode
    {
        static SparklingMoveNode()
        {
            allNodes = new Dictionary<SparklingMoveState, SparklingMoveNode>();
            var standardStates = new SparklingMoveState[]{
                SparklingMoveState.Fall,
                SparklingMoveState.Jump2,
                SparklingMoveState.Jump,
                SparklingMoveState.FallInitiate,
                SparklingMoveState.WallJump_0,
                SparklingMoveState.Move,
                SparklingMoveState.Climb,
                SparklingMoveState.Climb2,
            };
            allNodes[SparklingMoveState.Stall] = new SparklingMoveNode()
            {
                StateTime = 0,
                CanDoMove = (s, d) => true,
                PositionInterpolator = (s, t) => new Vector2(),
                NextDirectionWillChange = false,
                RelativeDestinationX = 0,
                RelativeDestinationY = 0,
                NextSates = standardStates
            };
            allNodes[SparklingMoveState.Fall] = new SparklingMoveNode()
            {
                StateTime = 1,
                CanDoMove = (s, d) => s.Level.IsReachableBySparkling(s, s.FromX, s.FromY + 1),
                RelativeDestinationX = 0,
                RelativeDestinationY = 1,
                NextSates = standardStates
            };
            allNodes[SparklingMoveState.FallInitiate] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed,
                CanDoMove = (s, d) => s.HoleInFront(d),
                PositionInterpolator = (s, t) => new Vector2(
                    XMoveSpeed * t,
                    (float)Math.Pow(t / XMoveSpeed, 2)
                    ),
                RelativeDestinationX = 1,
                RelativeDestinationY = 1,
                NextSates = standardStates
            };
            allNodes[SparklingMoveState.Jump] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed * 2,
                CanDoMove = (s, d) => s.CanJump(d),
                PositionInterpolator = (s, t) => new Vector2(
                    XMoveSpeed * t,
                    -0.25f + (float)Math.Pow(t / XMoveSpeed * 0.5f - 0.5f, 2)),
                RelativeDestinationX = 2,
                RelativeDestinationY = 0,
                NextSates = standardStates
            };
            allNodes[SparklingMoveState.Jump2] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed * 2,
                CanDoMove = (s, d) => s.CanJump2(d),
                PositionInterpolator = (s, t) => new Vector2(
                    t / XMoveSpeed * 1.5f,
                    -0.25f + (float)Math.Pow(t / XMoveSpeed * 0.5f - 0.5f, 2)),
                RelativeDestinationX = 3,
                RelativeDestinationY = 0,
                NextSates = standardStates
            };
            allNodes[SparklingMoveState.Climb] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed,
                CanDoMove = (s, d) => s.CanClimb(d),
                PositionInterpolator = (s, t) => new Vector2(
                    XMoveSpeed * t,
                    -1 + (float)Math.Pow(1 - t / XMoveSpeed, 2)
                    ),
                RelativeDestinationX = 1,
                RelativeDestinationY = -1,
                NextSates = standardStates
            };
            allNodes[SparklingMoveState.Climb2] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed * 1.5f,
                CanDoMove = (s, d) => s.CanClimb2(d),
                RelativeDestinationX = 0,
                RelativeDestinationY = -1,
                NextSates = new SparklingMoveState[]{
                    SparklingMoveState.Climb,
                    SparklingMoveState.Fall,
                }
            };
            allNodes[SparklingMoveState.WallJump_0] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed,
                CanDoMove = (s, d) => s.CanWallJump(d),
                NextDirectionWillChange = true,
                RelativeDestinationX = 1,
                RelativeDestinationY = -1,
                NextSates = new SparklingMoveState[]{
                    SparklingMoveState.WallJump_1,
                    SparklingMoveState.Fall,
                }
            };
            allNodes[SparklingMoveState.WallJump_1] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed,
                CanDoMove = (s, d) => s.CanClimb(d),
                NextDirectionWillChange = false,
                RelativeDestinationX = 1,
                RelativeDestinationY = -1,
                NextSates = standardStates
            };
            allNodes[SparklingMoveState.Move] = new SparklingMoveNode()
            {
                StateTime = XMoveSpeed,
                CanDoMove = (s, d) => s.Level.IsReachableBySparkling(s, s.FromX + d, s.FromY),
                RelativeDestinationX = 1,
                RelativeDestinationY = 0,
                NextSates = standardStates
            };
        }
        public static readonly Dictionary<SparklingMoveState, SparklingMoveNode> allNodes;
        public static readonly float XMoveSpeed = 1.0f;

        public SparklingMoveState GetNextState(Sparkling sparkling, ref int newDir)
        {
            foreach (SparklingMoveState state in NextSates)
            {
                SparklingMoveNode possibleNextNode = allNodes[state];
                if (possibleNextNode.CanDoMove(sparkling, sparkling.direction))
                {
                    return state;
                }
            }
            // Changing direction.
            foreach (SparklingMoveState state in NextSates)
            {
                SparklingMoveNode possibleNextNode = allNodes[state];
                if (possibleNextNode.CanDoMove(sparkling, -sparkling.direction))
                {
                    newDir *= -1;
                    return state;
                }
            }
            return SparklingMoveState.Stall;
        }

        public float StateTime;
        // float is the time since move start.
        public Func<Sparkling, float, Vector2> PositionInterpolator =
            (s, t) => new Vector2(
                s.moveState.RelativeDestinationX * t / s.moveState.StateTime,
                s.moveState.RelativeDestinationY * t / s.moveState.StateTime);
        public Func<Sparkling, int, bool> CanDoMove = (s, d) => false;
        public bool NextDirectionWillChange = false;
        public int RelativeDestinationX;
        public int RelativeDestinationY;
        public SparklingMoveState[] NextSates { get; private set; }
    }
    public enum SparklingMoveState
    {
        Stall,
        Move,
        Fall,
        FallInitiate,
        Jump,
        Jump2,
        Climb,
        Climb2,
        WallJump_0,
        WallJump_1
    }
    public class Sparkling : IRenderable
    {
        public static int DefaultParticleCount = 50;
        public RayColor Color
        {
            get { return color; }
            set
            {
                color = value != RayColor.Black ? value : RayColor.None;
                ParticleGenerator.CurrentlySpawnedColor = Color.ToColor();
            }
        }
        private RayColor color;
        internal Level Level;
        internal SparklingMoveNode moveState;
        public Vector2 Position
        {
            get
            {
                return new Vector2(FromX + 0.5f, FromY + 0.5f)
                    + moveState.PositionInterpolator(this, timeSinceMoveStart) * new Vector2(direction, 1);
            }
        }
        public Vector2 Speed { get { return (Position - lastPosition) / timeSinceLastPos; } }
        public int FromX, FromY;
        public int ToDirX, ToDirY;
        public int direction;
        public float timeSinceMoveStart;
        public bool Disappeared { get { return Disappearing && ParticleGenerator.CurrentParticleCount == 0; } }
        public bool Disappearing { get; private set; }
        private Vector2 lastPosition;
        private float timeSinceLastPos;
        public ParticleGenerator ParticleGenerator;

        public Sparkling(Level level, int posX, int posY, RayColor c)
        {
            Level = level;
            Disappearing = false;
            ParticleGenerator = new ParticleGenerator(level);
            ParticleGenerator.InitialSpeedCalculator = () =>
                ParticleGenerator.GetDefaultInitialSpeedCalculator(0.4f)() + Speed * 0.8f;

            Color = c;
            timeSinceMoveStart = 0;
            FromX = posX;
            FromY = posY;
            lastPosition = new Vector2(posX, posY);
            timeSinceLastPos = 1.0f;// Not 0
            direction = 1;
            moveState = SparklingMoveNode.allNodes[SparklingMoveState.Stall];
            NextState();
            LoadGraphicsRelatedElements();
        }
        public void LoadGraphicsRelatedElements()
        {
            ParticleGenerator.ParticleTexture = Level.ContentManager.Load<Texture2D>("Spark2");
            ParticleGenerator.ParticleLightTexture = Level.ContentManager.Load<Texture2D>("aura");
        }
        public void MakeDisappear()
        {
            ParticleGenerator.TargetParticleCount = 0;
            Disappearing = true;
        }
        public void PrepareRayHits(GameTime time)
        {
            timeSinceLastPos = (float)time.ElapsedGameTime.TotalSeconds;
            lastPosition = Position;
            timeSinceMoveStart = timeSinceMoveStart + timeSinceLastPos;
        }
        public void Update(GameTime time)
        {
            UpdateColor(time);
            UpdatePosition(time);
            UpdateParticles(time);
        }
        internal void UpdatePosition(GameTime time)
        {
            timeSinceLastPos = (float)time.ElapsedGameTime.TotalSeconds;
            if (timeSinceMoveStart >= moveState.StateTime)
            {
                timeSinceMoveStart = 0;
                FromX += ToDirX;
                FromY += ToDirY;

                var currentTile = Level.Tiles[(int)Position.X, (int)Position.Y] as FilterTile;
                if (currentTile != null)
                {
                    Color = Color.Filtered(currentTile.Color);
                }
                ForceUpdateColor();// Fixing an heisenbug.
                NextState();
            }
        }
        protected void NextState()
        {
            direction *= moveState.NextDirectionWillChange ? -1 : 1;
            moveState = SparklingMoveNode.allNodes[moveState.GetNextState(this, ref direction)];
            ToDirX = moveState.RelativeDestinationX * direction;
            ToDirY = moveState.RelativeDestinationY;
        }
        #region Move possibilities
        internal bool CanClimb(int d)
        {
            return Color.Contains(RayColor.Green)
                && !Level.IsReachableBySparkling(this, FromX + d, FromY)
                && Level.IsReachableBySparkling(this, FromX, FromY - 1)
                && Level.IsReachableBySparkling(this, FromX + d, FromY - 1);
        }
        internal bool CanClimb2(int d)
        {
            return Color.Contains(RayColor.Cyan)
                && !Level.IsReachableBySparkling(this, FromX + d, FromY)
                && !Level.IsReachableBySparkling(this, FromX + d, FromY - 1)
                && Level.IsReachableBySparkling(this, FromX, FromY - 1)
                && Level.IsReachableBySparkling(this, FromX, FromY - 2)
                && Level.IsReachableBySparkling(this, FromX + d, FromY - 2);
        }
        internal bool CanJump(int d)
        {
            return Color.Contains(RayColor.Red)
                && HoleInFront(d)
                && Level.IsReachableBySparkling(this, FromX + 2 * d, FromY);
        }
        internal bool CanJump2(int d)
        {
            return Color.Contains(RayColor.Purple)
                && HoleInFront(d)
                && HoleInFront(d * 2)
                && Level.IsReachableBySparkling(this, FromX + 3 * d, FromY);
        }
        internal bool CanWallJump(int d)
        {
            return Color.Contains(RayColor.Yellow)
                && Level.IsReachableBySparkling(this, FromX + d, FromY)
                && Level.IsReachableBySparkling(this, FromX + d, FromY - 1)
                && Level.IsReachableBySparkling(this, FromX + d, FromY - 2)
                && !Level.IsReachableBySparkling(this, FromX, FromY - 1)
                && Level.IsReachableBySparkling(this, FromX, FromY - 2)
                && !Level.IsReachableBySparkling(this, FromX + d * 2, FromY)
                && !Level.IsReachableBySparkling(this, FromX + d * 2, FromY - 1);
        }
        internal bool HoleInFront(int d)
        {
            return Level.IsReachableBySparkling(this, FromX + d, FromY)
                && Level.IsReachableBySparkling(this, FromX + d, FromY + 1);
        }
        #endregion
        internal void UpdateColor(GameTime time)
        {
            var currentTile = Level.Tiles[(int)Position.X, (int)Position.Y] as EmptyTile;
            if (currentTile != null)
            {
                Color = Color.Combined(currentTile.SparklingInducedColor);
            }
        }
        internal void ForceUpdateColor()
        {
            var currentTile = Level.Tiles[(int)Position.X, (int)Position.Y] as EmptyTile;
            if (currentTile != null)
            {
                Color = Color.Combined(currentTile.HitColor);
            }
        }
        private void UpdateParticles(GameTime time)
        {
            ParticleGenerator.Position = Position;
            ParticleGenerator.Update(time);
        }
        public void DrawBase(RenderContext rc) { }
        public void DrawAdditive(RenderContext rc)
        {
            ParticleGenerator.DrawParticles(rc);
        }
        public void DrawLight(RenderContext rc)
        {
            ParticleGenerator.DrawLight(rc);
        }
        public void DrawEmissive(RenderContext rc) { }
        public void OnHit(ColoredRay from, LightHittable segmentHit, Vector2 positionHit)
        {
            Color = Color.Combined(from.Color);
        }
        public List<LightHittable> LightSurfaces
        {
            get
            {
                Vector2 offset = new Vector2(0.01f, 0) * direction;
                return new LightHittable[] {
                    new LightHittable(lastPosition - offset, Position + offset*2, false){ Hit = OnHit }
                }.ToList();
            }
        }
    }
}
